<?php

use Elliptic\EC;
use Ethereum\Eth;
use Ethereum\Formatter;
use Ethereum\HecoApi;
use Ethereum\HRC20;
use Ethereum\PEMHelper;
use Ethereum\Utils;
use Ethereum\TransactionEvent;
use Web3p\EthereumTx\Transaction;

require_once WEB_PATH . "/../vendor/heco/autoload.php";

class Heco
{

    const HECO_KEY = ''; // 节点key
    const WALLET_PRIVATE_KEY = ''; // 密钥
    const WALLET_ADDRESS = ''; // 钱包地址

    const SUCCESS_TX_HASH = '';
    const FAIL_TX_HASH = '';

    /*
     *  获取火币余额
     * */
    public function balance($address)
    {

        $params = [$address, 'latest'];
        $res = $this->request("eth_getBalance", $params);
        if (!empty($res['result'])) {
            $balance = Utils::weiToEth(Utils::hexToDec($res['result']));
        }
        return $balance ?? 0;
    }

    /*
     *  获取代币精度
     *  @param $contract_address 代币合约地址
     * */
    public function contractDecimal($contract_address)
    {

        $cache_key = "contractDecimal_" . $contract_address;
        $decimals = \think\facade\Cache::get($cache_key);
        if ($decimals) {
            return $decimals;
        }
        $formatMethod = Formatter::toMethodFormat('decimals()');
        $data_arr = [
            'to' => $contract_address,
            'data' => "0x{$formatMethod}"
        ];
        $params = [$data_arr, 'latest'];
        $res = $this->request("eth_call", $params); //eth_call 用来调用合约函数
        if (!empty($res['result'])) {
            $decimals = Utils::hexToDec($res['result']);
            \think\facade\Cache::set($cache_key, $decimals);
        }
        return $decimals ?? 0;
    }

    /*
     *  获取代币余额
     *  @param $contract_address 代币合约地址
     *  @param $address 要查询的钱包地址
     * */
    public function contractBalance($address, $contract_address)
    {

        $formatMethod = Formatter::toMethodFormat('balanceOf(address)');
        $formatAddress = Formatter::toAddressFormat($address);
        $data_arr = [
            'from' => $address,
            'to' => $contract_address,
            'data' => "0x{$formatMethod}{$formatAddress}"
        ];
        $params = [$data_arr, 'latest'];
        $res = $this->request("eth_call", $params); //eth_call 用来调用合约函数
        if (!empty($res['result'])) {
            $balance = Utils::hexToDec($res['result']) / pow(10, $this->contractDecimal($contract_address));
        }
        $balance1 = 316227766016;
        echo $this->number_to_normal($balance1);
        echo '<br/>';
        echo $this->number_to_normal(($balance ?? 0));die;
        return $this->number_to_normal(($balance ?? 0));
    }

    /**
     * 将科学计数法的数字转换为正常的数字
     * 为了将数字处理完美一些，使用部分正则是可以接受的
     * @author loveyu
     * @param string $number
     * @return string
     */
    public function number_to_normal($number)
    {
        if(stripos($number, 'e') === false) {
            //判断是否为科学计数法
            return $number;
        }

        if(!preg_match(
            "/^([\\d.]+)[eE]([\\d\\-\\+]+)$/",
            str_replace(array(" ", ","), "", trim($number)), $matches)
        ) {
            //提取科学计数法中有效的数据，无法处理则直接返回
            return $number;
        }

        //对数字前后的0和点进行处理，防止数据干扰，实际上正确的科学计数法没有这个问题
        $data = preg_replace(array("/^[0]+/"), "", rtrim($matches[1], "0."));
        $length = (int)$matches[2];
        if($data[0] == ".") {
            //由于最前面的0可能被替换掉了，这里是小数要将0补齐
            $data = "0{$data}";
        }

        //这里有一种特殊可能，无需处理
        if($length == 0) {
            return $data;
        }

        //记住当前小数点的位置，用于判断左右移动
        $dot_position = strpos($data, ".");
        if($dot_position === false) {
            $dot_position = strlen($data);
        }

        //正式数据处理中，是不需要点号的，最后输出时会添加上去
        $data = str_replace(".", "", $data);


        if($length > 0) {
            //如果科学计数长度大于0

            //获取要添加0的个数，并在数据后面补充
            $repeat_length = $length - (strlen($data) - $dot_position);
            if($repeat_length > 0) {
                $data .= str_repeat('0', $repeat_length);
            }

            //小数点向后移n位
            $dot_position += $length;
            $data = ltrim(substr($data, 0, $dot_position), "0").".".substr($data, $dot_position);

        } elseif($length < 0) {
            //当前是一个负数

            //获取要重复的0的个数
            $repeat_length = abs($length) - $dot_position;
            if($repeat_length > 0) {
                //这里的值可能是小于0的数，由于小数点过长
                $data = str_repeat('0', $repeat_length).$data;
            }

            $dot_position += $length;//此处length为负数，直接操作
            if($dot_position < 1) {
                //补充数据处理，如果当前位置小于0则表示无需处理，直接补小数点即可
                $data = ".{$data}";
            } else {
                $data = substr($data, 0, $dot_position).".".substr($data, $dot_position);
            }
        }
        if($data[0] == ".") {
            //数据补0
            $data = "0{$data}";
        }
        return trim($data, ".");
    }

    /*
     *  代币转账
     *  @param $contractAddress 代币合约地址
     *  @param $from_address 转账钱包地址
     *  @param $privateKey 钱包密钥
     *  @param $transfer_num 转账数量
     * */
    public function contractTranfer($from_address,$transfer_num,$contractAddress,$privateKey,$to_adderss){

        $params = [
            'from' => $from_address,
            'to' => $contractAddress,
            'value' => '0x0',
//            'value' => Utils::decToHex($transfer_num*2),
        ];
        $transfer_num = $this->number_to_normal($transfer_num);
        $formatMethod = Formatter::toMethodFormat('transfer(address,uint256)');
        $formatAddress = Formatter::toAddressFormat($to_adderss);
        $formatInteger = $this->formatMoney($transfer_num,18);

//        $val = Utils::toMinUnitByDecimals("$transfer_num", 2);
//        $formatInteger = Formatter::toIntegerFormat($val);

        $params['data'] = "0x{$formatMethod}{$formatAddress}{$formatInteger}";
//        echo $transfer_num;
//        echo '<br/>';
//        $this->getGas($params);
//        echo Utils::ethToWei($transfer_num);die;

        $gas = $this->getGas($params);
        $gasPrice = $this->getGasPrice();
        $params['gas'] = "{$gas}";
        $params['nonce'] = $this->getNonce($from_address);
        $params['gasPrice'] =  "{$gasPrice}";
        $res = $this->sendRawTransaction($params,$privateKey);
        return $res;
    }

    /*
     *  为签名交易创建一个新的消息调用交易或合约。
     *  @param $data string 签名的交易数据
     * */
    public function sendRawTransaction($transaction,$privateKey){
//        if(isset($transaction['from'])){
//            unset($transaction['from']);
//        }
        $chainId = 128;
        // 合并数据
        $transaction = array_merge([
            'nonce' => '01',
            'gasPrice' => '',
            'gas' => '',
            'to' => '',
            'value' => '',
            'data' => '',
            'chainId' => $chainId,
        ], $transaction);
        echo '<pre/>';
//        print_r($transaction);die;
        $transaction = new Transaction($transaction);
        $raw = $transaction->sign($privateKey);

        $res = $this->request("eth_sendRawTransaction", [Utils::add0x($raw)]);
        echo 66;
        dump($res);
        die;

        $res = $this->proxyApi->sendRawTransaction('0x'.$raw);
        // serialize
        $raw = $this->rawEncode($transaction);

        // sign
        $signature = $this->sign($raw,$privateKey);
        // 按照这个顺序，不然序列会错误
        $transaction['v'] = Utils::decToHex($signature->recoveryParam + 27 + ($chainId ? $chainId * 2 + 8 : 0));
        $transaction['r'] = $signature->r->toString('hex');
        $transaction['s'] = $signature->s->toString('hex');
        // 签署的RAW
        $signRaw = $this->rawEncode($transaction);
//        echo 66;die;
        // 发送交易
        $res = $this->request("eth_sendRawTransaction", [Utils::add0x($signRaw)]);
        dump($res);die;
    }

    /**
     * 对交易数据进行签名
     * @param $pri 十六进制私钥
     * @param $msg 十六进制数据
     * @return $signature
     */
    public function sign($data,$privateKey)
    {
        // sha1
        $hash = kornrunner\Keccak::hash(hex2bin($data), 256);

        $ec = new EC('secp256k1');
        // Generate keys
        $key = $ec->keyFromPrivate($privateKey);
        // Sign message (can be hex sequence or array)
        $signature = $key->sign($hash, ['canonical' => true]);

        // Verify signature
        return $signature;
    }

    /**
     * RLPencode
     */
    public function rawEncode(array $input): string
    {
        $rlp = new Web3p\RLP\RLP;
        $data = [];
//        echo '<pre/>';
//        print_r($input);
        foreach ($input as $item) {
            // 如果值是无效值：0、0x0，将其列为空串
            $data[] = $item && Utils::hexToDec(Utils::remove0x($item)) != 0 ? Utils::add0x($item) : '';
        }
//        print_r($data);die;
        return $rlp->encode($data)->toString('hex');
    }

    /*
     *  formatMoney()
     * */
    public function formatMoney($money){
        return Utils::fill0(Utils::remove0x(Utils::decToHex($money*100)));
        return Utils::fill0(Utils::remove0x(Utils::decToHex(Utils::ethToWei($money))));

    }

    /*
    *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/6/
    *  当前的gas价格，单位：wei。
     * return 十六进制
    * */
    public function getGas($data){
        $params = [$data];
        $res = $this->request("eth_estimateGas", $params);
        if(!empty($res['result'])){
            $gas = Utils::hexToDec($res['result']) * 1.5;
        }
        return Utils::decToHex(($gas ?? 0));
    }

    /*
    *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/6/
    *  当前的gas价格，单位：wei。
     * return 十六进制
    * */
    public function getGasPrice(){
        $res = $this->request("eth_gasPrice");
        return $res['result'] ?? '';
    }

    public function getNonce($address){
        $params = [$address, 'latest'];
        $res = $this->request("eth_getTransactionCount", $params);
        return $res['result'] ?? '';
//        if (!empty($res['result'])) {
//            $nonce = Utils::hexToDec($res['result']);
//            $nonce = Utils::decToHex($nonce);
//        }
//        return $nonce ?? 0;
    }

    /*
     *  @api:http://cw.hubwiz.com/card/c/ethereum-json-rpc-api/1/3/10/
     * 指定地址发生的交易数量
     * */
    public function getNonce_old($address){
        $params = [$address, 'pending'];
        $res = $this->request("eth_getTransactionCount", $params);
        if (!empty($res['result'])) {
            $nonce = Utils::hexToDec($res['result']);
        }
        return $nonce ?? 0;
    }

    /*
     *  curl 请求
     * */
    private function request($methond, $params = [])
    {
        $api_url = "https://http-mainnet.hecochain.com";

        $send_data = [
            'jsonrpc' => '2.0',
            'method' => $methond,
            'id' => 67,
            'params' => $params
        ];
        $curl = curl_init();
//        0x4a65ebc558b599e89668aabfcdbb6ac72eacea73f8adb7e1b7a9f26c3902a876
//        0x4a65ebc558b599e89668aabfcdbb6ac72eacea73f8adb7e1b7a9f26c3902a876
//        0x4a65ebc558b599e89668aabfcdbb6ac72eacea73f8adb7e1b7a9f26c3902a876

        curl_setopt_array($curl, array(
            CURLOPT_URL => $api_url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_ENCODING => '',
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
//            CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20100101 Firefox/12.0",
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_TIMEOUT => 30,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST => 'POST',
            CURLOPT_POSTFIELDS => json_encode($send_data),
            CURLOPT_HTTPHEADER => array(
                'Content-Type: application/json'
            ),
        ));
        $response = curl_exec($curl);
        curl_close($curl);
        return json_decode($response, 1);
    }
}

?>